package ar.com.temporis.framework.common.comparator;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import ar.com.temporis.framework.common.comparator.logger.ComparatorLogger;

/**
 * Abstract implementation of {@code BeanComparator} that manages the basic
 * cases, like comparing the same object or comparing null.
 * 
 * @author raul.bajales@gmail.com
 */
public abstract class AbstractBeanComparator implements BeanComparator {

  private static final String GETTER_PREFIX = "get";

  private boolean result = true;

  protected ComparatorLogger logger;

  public AbstractBeanComparator(ComparatorLogger logger) {
    this.logger = logger;
  }

  /**
   * Adds a message to the logs that shows the partial results for the
   * comparison.
   * 
   * @param message The message to add to the log.
   */
  protected void addToLog(String message) {
    logger.addToLog(message);
  }
  
  public String getLog() {
    return logger.getLogAsString();
  }

  /**
   * Sets the boolean value that indicates if the verification was successful or
   * not.
   * 
   * @param result The value to set.
   */
  protected void setResult(boolean result) {
    this.result = result;
  }

  public final boolean comparisonResult() {
    return this.result;
  }

  public void compare(Object bean1, Object bean2) {
    if (bean1 == bean2 && bean1 != null) {
      setResult(true);
      return;
    }
    if (bean1 != null && bean2 != null
        && !bean1.getClass().equals(bean2.getClass())) {
      addToLog("The beans to verify are of different class!");
      setResult(false);
      return;
    }
    doComparison(bean1, bean2);
  }

  /**
   * Extracts the value of a field only if the field is accessible (it means,
   * only if the field is public or it has a public getter method).
   * 
   * @param field The field from which to extract the value.
   * @param obj The object that has the field.
   * @return The value of the given field for the given instance.
   * @throws ExtractFieldValueException If the field value cannot be extracted.
   */
  protected Object extractFieldValue(Field field, Object obj)
      throws ExtractFieldValueException {
    Object retValue;
    try {
      retValue = field.get(obj);
    } catch (IllegalAccessException e) {
      String fieldName = field.getName();
      String getterMethodName =
          GETTER_PREFIX + fieldName.substring(0, 1).toUpperCase()
              + fieldName.substring(1, fieldName.length());
      try {
        Method getter =
            obj.getClass().getMethod(getterMethodName, new Class[] {});
        retValue = getter.invoke(obj);
      } catch (NoSuchMethodException e1) {
        throw new ExtractFieldValueException();
      } catch (IllegalAccessException e1) {
        throw new ExtractFieldValueException();
      } catch (InvocationTargetException e1) {
        throw new ExtractFieldValueException();
      }
    }
    return retValue;
  }

  /**
   * To be re-implemented in the inherited classes that makes the comparison.
   * 
   * @param bean1 The first bean.
   * @param bean2 The second bean.
   */
  protected abstract void doComparison(Object bean1, Object bean2);
}
